# /**
# * Copyright (c) 2022 Beijing Jiaotong University
# * PhotLab is licensed under [Open Source License].
# * You can use this software according to the terms and conditions of the [Open Source License].
# *
# * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
# *
# * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# *
# * See the [Open Source License] for more details.
# */
# /**
# * Author: Shihao Xie
# * Created: Jul. 15, 2022
# * Supported by: National Key Research and Development Program of China
# */
import os.path

from phot_engine.engine_shell.graph_entity import GraphEntity
from phot_server.rpc.device_data_type.ttypes import PhotGrid, PhotWaveguide, PhotSource, PhotDetector, PhotMaterial, \
    PhotMaterialProperty
from phot_server.rpc.engine_data_type.ttypes import PhotOutput, PhotAnalyzerOutput
from phot_server.rpc.file_data_type.ttypes import PhotFile
from phot_server.rpc.project_data_type.ttypes import ProjectConfig
from phot_server.rpc.graph_data_type.ttypes import PhotGraph, RenderGraph
from phot_server.server_config.global_config import ICON_ROOT
from phot_server.utils.generate_uuid import generate_uuid
from phot_server.repository.material_repository import MaterialRepository

COMPONENT_MAPPING = {
    "componentId": "componentId",
    "ptName": "ptName",
    "calculationMethod": "calculationMethod",
    "parallelComputingMethod": "parallelComputingMethod",
    "isAnalyzer": "isAnalyzer",
    "paramList":
        {
            "paramList": {
                "pyPhotName": "pyPhotName",
                "dataType": "dataType",
                "value": "value",
                "unit": "unit",
                "photUnit": "photUnit"
            }
        }
}

EDGE_MAPPING = {
    "sourceVertexId": "sourceVertexId",
    "sourcePortId": "sourcePortId",
    "destinationVertexId": "destinationVertexId",
    "destinationPortId": "destinationPortId",
    "signalType": "signalType"
}


def graph_dict2GraphEntity(graph_dict, parse_graph_parameter=False):
    """

    :param graph_dict:
    :param parse_graph_parameter:
    :return:
    """
    graph_entity = GraphEntity(scrutiny=False)
    for component in graph_dict["components"]:
        graph_entity.add_vertex(dict_to_dict_by_map(component, COMPONENT_MAPPING))
    for link in graph_dict["links"]:
        graph_entity.add_edge(dict_to_dict_by_map(link, EDGE_MAPPING))
    if parse_graph_parameter:
        graph_entity.set_system_global_parameter(graph_dict["graphParameters"])
    # graph_entity.print_graph_entity_meta_data(show_detail=True)
    return graph_entity


def dict_to_dict_by_map(source_dict: dict, mapping: dict) -> dict:
    result_dict = {}
    for key, value in mapping.items():
        if key in source_dict:
            if isinstance(source_dict[key], list):
                result_dict[list(value.keys())[0]] = []
                for item in source_dict[key]:
                    result_dict[list(value.keys())[0]].append(dict_to_dict_by_map(item, list(value.values())[0]))
            elif isinstance(source_dict[key], dict):
                result_dict[list(value.keys())[0]] = dict_to_dict_by_map(source_dict[key], list(value.values())[0])
            else:
                result_dict[value] = source_dict[key]
        else:
            raise Exception("Object missing a key named '{}'.".format(key))
    return result_dict


def count_port(port_list):
    port_count = {"input": 0, "output": 0}
    for port in port_list:
        if port.direction == PortDirection.IN:
            port_count["input"] += 1
        elif port.direction == PortDirection.OUT:
            port_count["output"] += 1
    return port_count


def PhotGraph_to_GraphEntity(phot_graph: PhotGraph, render_graph: RenderGraph):
    """
    Convert PhotGraph, used in transportation between front end and back end,
    to GraphEntity that is the graph type in compute endgine
    :param phot_graph: PhotGraph object
    :return: GraphEntity object
    """
    # Handle server_config parameter
    # global_parameter_list = dict()
    # if phot_graph.graphParameters is not None and len(phot_graph.graphParameters) != 0:
    #     parameter_dict = {}
    #     for parameter in phot_graph.graphParameters:
    #         if parameter.category is not None and parameter.category != "":
    #             category = parameter.category
    #         else:
    #             category = "systemGlobalSimulationParameter"
    #         if category not in parameter_dict:
    #             parameter_dict[category] = []
    #         parameter_dict[parameter.category].append({"pyPhotName": parameter.name,
    #                                                    "value": parameter.value,
    #                                                    "dataType": parameter.dataType,
    #                                                    "unit": parameter.unit if parameter.unit is not None else "",
    #                                                    "photUnit": parameter.photUnit if parameter.photUnit is not None else ""})
    #     # global_parameter_list["globalParamList"] = PhotParameters_to_list(phot_graph.graphParameters)
    #     global_parameter_list["globalParamList"] = parameter_dict

    global_parameter_list = PhotParameters_to_list(phot_graph.graphParameters)

    id_port_dict = {}
    for node in render_graph.nodes:
        id_port_dict[node.nodeId] = count_port(node.ports)

    # Handle component
    vertex_list = []
    for phot_component in phot_graph.components:
        component_data = dict()
        for attr in phot_component.__dict__:
            if attr == "name":
                component_data["ptName"] = str(getattr(phot_component, attr))
            elif attr != "parameters":
                component_data[attr] = str(getattr(phot_component, attr))
            else:
                # print(phot_component.name)
                component_data["paramList"] = PhotParameters_to_list(getattr(phot_component, attr))
        vertex_list.append(component_data)

    # Handle edge
    edge_list = []
    for edge in phot_graph.links:
        edge_list.append({"sourceVertexId": str(edge.srcNodeId),
                          "sourcePortId": str(edge.srcPortId - id_port_dict[edge.srcNodeId]["input"]),
                          "destinationVertexId": str(edge.dstNodeId),
                          "destinationPortId": str(edge.dstPortId),
                          "signalType": ""})

    # New GraphEntity object
    graph_entity = GraphEntity()
    # graph_entity.print_graph_entity_meta_data(show_detail=True)
    for component in vertex_list:
        graph_entity.add_vertex(component)
    for edge in edge_list:
        graph_entity.add_edge(edge)
    graph_entity.set_system_global_parameter(global_parameter_list)

    return graph_entity


def PhotParameters_to_list(parameters):
    if parameters is None:
        return []
    parameter_list = []
    if isinstance(parameters, list):
        for parameter in parameters:
            parameter_dict = {"pyPhotName": parameter.name,
                              "value": parameter.value,
                              "dataType": parameter.dataType,
                              "unit": parameter.unit if parameter.unit is not None else "",
                              "photUnit": parameter.photUnit if parameter.photUnit is not None else ""}
            parameter_list.append(parameter_dict)
    elif isinstance(parameters, dict):
        for params in parameters.values():
            for parameter in params:
                parameter_dict = {"pyPhotName": parameter.name,
                                  "value": parameter.value,
                                  "dataType": parameter.dataType,
                                  "unit": parameter.unit if parameter.unit is not None else "",
                                  "photUnit": parameter.photUnit if parameter.photUnit is not None else ""}
                parameter_list.append(parameter_dict)
    return parameter_list


from phot_server.core.exception import ParseError, ResourceNotFoundError
from phot_server.rpc.graph_data_type.ttypes import PhotGraph, PhotParameter, PhotComponent, RenderPort, PhotGraphLink, \
    RenderTextArea, RenderGraphNode, RenderRect, RenderWayPoint, RenderGraph, RenderRoute, PortDirection, PortAspect
from phot_server.rpc.library_data_type.ttypes import PhotComponentPrototype


def dict_to_RenderPort_list(port_dict: dict):
    if port_dict is None:
        return None
    ports = []
    loc_point = [[], [2], [1, 3], [1, 2, 3], [0, 1, 2, 3]]
    port_id = 0
    for direction in port_dict.keys():
        location = 0
        for phot_name, front_name in port_dict[direction].items():
            port = RenderPort(portId=port_id,
                              location=loc_point[len(port_dict[direction])][location],
                              direction=PortDirection.IN if direction == "inputs" else PortDirection.OUT,
                              aspect=PortAspect.LEFT if direction == "inputs" else PortAspect.RIGHT,
                              name=phot_name,
                              description=front_name)
            port_id += 1
            location += 1
            ports.append(port)
    return ports


def dict_to_ProjectConfig(project_config_dict: dict) -> ProjectConfig:
    try:
        content = project_config_dict["PhotProject"]
        return ProjectConfig(projectId=content["projectId"],
                             projectName=content["projectName"],
                             createTime=content.get("createTime", None),
                             projectDirectory=content.get("projectDirectory", None),
                             projectType=content["projectType"],
                             mainFileName=content["mainFileName"],
                             description=content.get("description", None),
                             files=list_to_PhotFile_list(content.get("files", [])),
                             windowConfig=content.get("windowConfig", None))
    except KeyError as e:
        ParseError("Missing attribute in parameter ", e)


# def list_to_PhotParameter_list(parameter_list: list):
#     if parameter_list is None:
#         return None
#     parameters = []
#     for graph_parameter in parameter_list:
#         try:
#             phot_parameter = PhotParameter(name=graph_parameter.get("name", None),
#                                            dataType=graph_parameter.get("dataType", "float"),
#                                            value=graph_parameter.get("value", None),
#                                            unit=graph_parameter.get("unit", ""),
#                                            photUnit=graph_parameter.get("photUnit", ""),
#                                            nameTag=graph_parameter.get("nameTag", None),
#                                            enumItem=graph_parameter.get("enumItem", None),
#                                            interval=graph_parameter.get("interval", None),
#                                            category=graph_parameter.get("category", None),
#                                            description=graph_parameter.get("description", None))
#             parameters.append(phot_parameter)
#         except KeyError as e:
#             ParseError("Missing attribute in parameter ", e)
#     return parameters
#
#
# def dict_to_PhotParameter_list(parameter_dict: dict):
#     if parameter_dict is None:
#         return None
#     parameters = []
#     for category, graph_parameters in parameter_dict.items():
#         for graph_parameter in graph_parameters:
#             try:
#                 phot_parameter = PhotParameter(name=graph_parameter.get("name", None),
#                                                dataType=graph_parameter.get("dataType", "float"),
#                                                value=graph_parameter.get("value", None),
#                                                unit=graph_parameter.get("unit", ""),
#                                                photUnit=graph_parameter.get("photUnit", ""),
#                                                nameTag=graph_parameter.get("nameTag", None),
#                                                enumItem=graph_parameter.get("enumItem", None),
#                                                interval=graph_parameter.get("interval", None),
#                                                category=category,
#                                                description=graph_parameter.get("description", None))
#                 parameters.append(phot_parameter)
#             except KeyError as e:
#                 ParseError("Missing attribute in parameter ", e)
#     return parameters

def list_to_PhotParameter_dict(parameter_list: list):
    if parameter_list is None:
        return None
    parameters = {}
    for graph_parameter in parameter_list:
        try:
            phot_parameter = PhotParameter(name=graph_parameter.get("name", None),
                                           dataType=graph_parameter.get("dataType", "float"),
                                           value=graph_parameter.get("value", None),
                                           unit=graph_parameter.get("unit", ""),
                                           photUnit=graph_parameter.get("photUnit", ""),
                                           nameTag=graph_parameter.get("nameTag", None),
                                           enumItem=graph_parameter.get("enumItem", None),
                                           interval=graph_parameter.get("interval", None),
                                           category=graph_parameter.get("category", None),
                                           description=graph_parameter.get("description", None))
            category = graph_parameter.get("category", "physical")
            if category not in parameters.keys():
                parameters[category] = []
            parameters[category].append(phot_parameter)
        except KeyError as e:
            ParseError("Missing attribute in parameter ", e)
    if parameters.get("physical"):
        return parameters['physical']
    else:
        return parameters


def dict_to_PhotParameter_dict(parameter_dict: dict):
    if parameter_dict is None:
        return None
    parameters = {}
    for category, graph_parameters in parameter_dict.items():
        for graph_parameter in graph_parameters:
            try:
                phot_parameter = PhotParameter(name=graph_parameter.get("name", None),
                                               dataType=graph_parameter.get("dataType", "float"),
                                               value=graph_parameter.get("value", None),
                                               unit=graph_parameter.get("unit", ""),
                                               photUnit=graph_parameter.get("photUnit", ""),
                                               nameTag=graph_parameter.get("nameTag", None),
                                               enumItem=graph_parameter.get("enumItem", None),
                                               interval=graph_parameter.get("interval", None),
                                               category=category,
                                               description=graph_parameter.get("description", None))
                if category not in parameters.keys():
                    parameters[category] = []
                parameters[category].append(phot_parameter)
            except KeyError as e:
                ParseError("Missing attribute in parameter ", e)
    return parameters


def list_to_PhotComponent_list(component_list: list):
    if component_list is None:
        return None
    components = []
    for component in component_list:
        try:
            phot_component = PhotComponent(
                componentId=component.get("componentId", None) if isinstance(component.get("componentId", None),
                                                                             int) else generate_uuid(
                    component.get("componentId", None)),
                name=component.get("name", None),
                calculationMethod=component.get("calculationMethod", None),
                parallelComputingMethod=component.get("parallelComputingMethod", None),
                isAnalyzer=component.get("isAnalyzer", None),
                isClass=component.get("isClass", None),
                description=component.get("description", None),
                category=component.get("category", None),
                path=component.get("path", None),
                parameters=list_to_PhotParameter_dict(component.get("parameters", []))
                            if isinstance(component.get("parameters", None), list)
                               or component.get("parameters", None) is None
                            else dict_to_PhotParameter_dict(component.get("parameters", None)))
            components.append(phot_component)
        except KeyError as e:
            ParseError("Missing attribute in component ", e)
    return components


def list_to_PhotGraphLink_list(link_list: list, component_list: list):
    if link_list is None:
        return None
    links = []
    component_input_count = {}
    for component in component_list:
        component_id = component.get("componentId", None) if isinstance(component.get("componentId", None), int) \
            else generate_uuid(component.get("componentId", None))
        input_count = 0
        for port in component.get("ports", []):
            if port.get("direction", None) is not None and port.get("direction", None) == 0:
                input_count += 1
        component_input_count[component_id] = input_count

    for link in link_list:
        try:
            link_id = link.get("linkId", None) if isinstance(link.get("linkId", None), int) else generate_uuid(
                link.get("linkId", None))
            src_node_id = link.get("srcNodeId", None) if isinstance(link.get("srcNodeId", None),
                                                                    int) else generate_uuid(link.get("srcNodeId", None))
            src_port_id = int(link.get("srcPortId", None)) + component_input_count[src_node_id]
            dst_node_id = link.get("dstNodeId", None) if isinstance(link.get("dstNodeId", None),
                                                                    int) else generate_uuid(link.get("dstNodeId", None))
            dst_port_id = int(link.get("dstPortId", None))
            phot_link = PhotGraphLink(link_id, src_node_id, src_port_id, dst_node_id, dst_port_id)
            links.append(phot_link)
        except KeyError as e:
            ParseError("Missing attribute in link ", e)
    return links


def list_to_RenderGraphNode_list(component_list: list):
    if component_list is None:
        return None
    render_nodes = []
    for component in component_list:
        try:
            render_node = RenderGraphNode(
                nodeId=component.get("componentId", None) if isinstance(component.get("componentId", None),
                                                                        int) else generate_uuid(
                    component.get("componentId", None)),
                iconName=component.get("iconName", None),
                description=component.get("description", None),
                rect=dict_to_RenderRect(component.get("rect", None)),
                ports=list_to_RenderPort_list(component.get("ports", None)))
            render_nodes.append(render_node)
        except KeyError as e:
            ParseError("Missing attribute in render component ", e)
    return render_nodes


def dict_to_RenderRect(component_dict: dict):
    try:
        return RenderRect(x=component_dict.get("x", None),
                          y=component_dict.get("y", None),
                          width=component_dict.get("width", None),
                          height=component_dict.get("height", None))
    except KeyError as e:
        ParseError("Missing attribute in render component", e)


def list_to_RenderPort_list(port_list: list):
    if port_list is None:
        return None
    render_ports = []
    for port in port_list:
        try:
            render_port = RenderPort(portId=port["portId"],
                                     direction=port.get("direction", None),
                                     aspect=port.get("aspect", None),
                                     location=port.get("location", None),
                                     name=port.get("name", None),
                                     description=port.get("description", None))
            render_ports.append(render_port)
        except KeyError as e:
            ParseError("Missing attribute in render port ", e)
    return render_ports


def list_to_RenderRoute_list(link_list: list):
    if link_list is None:
        return None
    routes = []
    for link in link_list:
        try:
            render_route = RenderRoute(
                routeId=link.get("linkId", None) if isinstance(link.get("linkId", None), int) else generate_uuid(
                    link.get("linkId", None)),
                path=list_to_RenderWayPoint_list(link.get("points", None)))
            routes.append(render_route)
        except KeyError as e:
            ParseError("Missing attribute in link ", e)
    return routes


def list_to_RenderWayPoint_list(point_list: list):
    if point_list is None:
        return None
    render_points = []
    for point in point_list:
        try:
            render_point = RenderWayPoint(x=point.get("x", None),
                                          y=point.get("y", None),
                                          direction=point.get("direction", None)
                                          if point.get("direction", None) is not None
                                          else point.get("fixed", None))
            render_points.append(render_point)
        except KeyError as e:
            ParseError("Missing attribute in render path ", e)
    return render_points


def list_to_RenderTextArea_list(text_list: list):
    if text_list is None:
        return None
    render_texts = []
    for text in text_list:
        try:
            render_text = RenderTextArea(
                textId=text.get("textId", None) if isinstance(text.get("textId", None), int) else generate_uuid(
                    text.get("textId", None)),
                text=text.get("text", None),
                textColor=text.get("textColor", None),
                font=text.get("font", None),
                fontSize=text.get("fontSize", None),
                rect=dict_to_RenderRect(text.get("rect", None)),
                horizontalAlign=text.get("horizontalAlign", None),
                verticalAlign=text.get("verticalAlign", None),
                fillColor=text.get("fillColor", None),
                lineColor=text.get("lineColor", None),
                lineWeight=text.get("lineWeight", None))
            render_texts.append(render_text)
        except KeyError as e:
            ParseError("Missing attribute in render path ", e)
    return render_texts


def list_to_PhotFile_list(file_list: list):
    if file_list is None:
        return None
    phot_files = []
    for file in file_list:
        try:
            phot_file = PhotFile(path=file["path"],
                                 name=file["name"],
                                 type=file.get("type", None),
                                 description=file.get("description", None),
                                 fileSize=file.get("fileSize", None),
                                 lastAccessTime=file.get("lastAccessTime", None),
                                 lastModificationTime=file.get("lastModificationTime", None),
                                 createTime=file.get("createTime", None))
            phot_files.append(phot_file)
        except KeyError as e:
            ParseError("Missing attribute in render path ", e)
    return phot_files


def ProjectConfig_to_dict(project_config: ProjectConfig) -> dict:
    content = object_to_dict(project_config)
    for file in content["files"]:
        file["path"] = "./" + os.path.relpath(file["path"], content["projectDirectory"])
    content.pop("projectDirectory")
    project_config_dict = {"PhotProject": content}
    return project_config_dict


def PhotComponentPrototype_to_PhotComponent(prototype: PhotComponentPrototype, component_id) -> PhotComponent:
    return PhotComponent(componentId=component_id,
                         name=prototype.name,
                         calculationMethod=prototype.calculationMethod,
                         parallelComputingMethod=prototype.parallelComputingMethod,
                         isAnalyzer=prototype.isAnalyzer,
                         isClass=prototype.isClass,
                         description=prototype.description,
                         category=prototype.category,
                         path=prototype.path,
                         parameters=prototype.parameters)


def PhotComponentPrototype_to_RenderGraphNode(prototype: PhotComponentPrototype, component_id, rect) -> RenderGraphNode:
    return RenderGraphNode(nodeId=component_id,
                           componentName=prototype.name,
                           iconName=prototype.iconName,
                           iconPath="" if prototype.iconName is None else os.path.join(ICON_ROOT, prototype.iconName),
                           description=prototype.description,
                           rect=rect,
                           ports=prototype.ports)


def PhotGrid_to_dict(grid: PhotGrid):
    phot_grid_dict = object_to_dict(grid,
                                    ["PhotGrid", "PhotMaterial", "PhotMaterialProperty", "PhotDetector", "PhotSource"])
    phot_grid_dict["waveguides"] = [PhotWaveguide_to_dict(waveguide=waveguide) for waveguide in grid.waveguides]
    phot_grid_dict.pop("gridPath", None)
    grid_dict = {"Grid": phot_grid_dict}
    return grid_dict


def PhotWaveguide_to_dict(waveguide: PhotWaveguide):
    phot_waveguide_dict = object_to_dict(waveguide)
    if phot_waveguide_dict.get("material", None) is not None:
        phot_waveguide_dict["material"].pop("properties")
    # extraParameters = phot_waveguide_dict.pop("extraParameters")
    # for key, value in extraParameters.items():
    #     phot_waveguide_dict[key] = value
    return phot_waveguide_dict


def PhotSource_to_dict(source: PhotSource):
    return object_to_dict(source)


def PhotDetector_to_dict(detector: PhotDetector):
    return object_to_dict(detector)


def PhotMaterial_to_dict(material: PhotMaterial):
    return object_to_dict(material)


def dict_to_PhotGrid(grid_dict):
    try:
        return PhotGrid(gridId=grid_dict.get("gridId", generate_uuid()),
                        name=grid_dict.get("name", "Grid"),
                        xLength=grid_dict["xLength"],
                        yLength=grid_dict["yLength"],
                        zLength=grid_dict["zLength"],
                        gridSpacing=grid_dict["gridSpacing"],
                        totalTime=grid_dict["totalTime"],
                        pmlWidthX=grid_dict["pmlWidthX"],
                        pmlWidthY=grid_dict["pmlWidthY"],
                        pmlWidthZ=grid_dict["pmlWidthZ"],
                        permittivity=grid_dict["permittivity"],
                        permeability=grid_dict["permeability"],
                        courantNumber=grid_dict.get("courantNumber", None),
                        sources=[dict_to_PhotSource(source) for source in grid_dict.get("material", [])],
                        waveguides=[dict_to_PhotWaveguide(waveguide) for waveguide in grid_dict.get("waveguides", [])],
                        detectors=[dict_to_PhotDetector(detector) for detector in grid_dict.get("detectors", [])]
                        )
    except KeyError as e:
        raise ParseError("Missing attribute in graph ", e)


def dict_to_PhotMaterial(material_dict):
    try:
        if material_dict is None:
            return None
        material = PhotMaterial(name=material_dict["name"])
        material_repository = MaterialRepository()
        material.color = material_repository.get_material_color(material_dict["name"])
        material.properties = [PhotMaterialProperty(propertyId=pid, waveLength=waveLength, real=re, imag=im)
                               for pid, waveLength, re, im in material_repository.
                               get_material_refractive_index(material_name=material_dict["name"])]
        return material
    except KeyError as e:
        raise ParseError("Missing attribute in graph ", e)


def dict_to_PhotSource(source_dict):
    try:
        return PhotSource(sourceId=source_dict["sourceId"],
                          xLength=source_dict["xLength"],
                          yLength=source_dict["yLength"],
                          zLength=source_dict["zLength"],
                          x=source_dict["x"],
                          y=source_dict["y"],
                          z=source_dict["z"],
                          sourceType=source_dict["sourceType"],
                          period=source_dict["period"],
                          waveLength=source_dict.get("waveLength", None),
                          amplitude=source_dict["amplitude"],
                          phaseShift=source_dict["phaseShift"],
                          name=source_dict["name"],
                          pulse=source_dict["pulse"],
                          cycle=source_dict["cycle"],
                          hanningDt=source_dict["hanningDt"],
                          polarization=source_dict["polarization"]
                          )
    except KeyError as e:
        raise ParseError("Missing attribute in graph ", e)


def dict_to_PhotWaveguide(waveguide_dict):
    try:
        return PhotWaveguide(waveguideId=waveguide_dict["waveguideId"],
                             xLength=waveguide_dict["xLength"],
                             yLength=waveguide_dict["yLength"],
                             zLength=waveguide_dict["zLength"],
                             x=waveguide_dict["x"],
                             y=waveguide_dict["y"],
                             z=waveguide_dict["z"],
                             width=waveguide_dict["width"],
                             refractiveIndex=waveguide_dict["refractiveIndex"],
                             className=waveguide_dict["className"],
                             material=dict_to_PhotMaterial(waveguide_dict.get("material", None)),
                             name=waveguide_dict["name"],
                             )
    except KeyError as e:
        raise ParseError("Missing attribute in graph ", e)


def dict_to_PhotDetector(detector_dict):
    try:
        return PhotDetector(detectorId=detector_dict["detectorId"],
                            xLength=detector_dict["xLength"],
                            yLength=detector_dict["yLength"],
                            zLength=detector_dict["zLength"],
                            x=detector_dict["x"],
                            y=detector_dict["y"],
                            z=detector_dict["z"],
                            name=detector_dict["name"],
                            detectorType=detector_dict["detectorType"],
                            )
    except KeyError as e:
        raise ParseError("Missing attribute in graph ", e)


def dict_to_PhotOutput(output_dict):
    data = output_dict["PhotOutput"]
    output = PhotOutput(componentId=data["componentId"], portId=data["componentId"], polarization=data["componentId"],
                        signalType=data["componentId"])


def dict_to_PhotAnalyzerOutput(output_dict):
    data_list = output_dict["PhotAnalyzerOutput"]
    output_list = []
    for data in data_list:
        output = PhotAnalyzerOutput(graphId=data["graphId"], analyzerId=data["analyzerId"],
                                    plotType=data["plotType"], shape=data["shape"], title=data["title"])


def dict_to_PhotSignal(signal_dict):
    pass


def dict_to_PhotArray(array_dict):
    pass


OBJECT_LIST = ["PhotGraph", "PhotParameter", "PhotComponent", "PhotGraphLink", "RenderTextArea", "ProjectConfig",
               "PhotComponentPrototype", "RenderWayPoint", "RenderRoute", "RenderPort", "RenderRect", "RenderGraph",
               "PhotFile", "PhotFileType", "ProjectConfig", "ProjectType", "PhotSignalType", "PhotOutput", "PhotSignal",
               "PhotArray", "PhotComplexDouble", "PhotGrid", "PhotWaveguide", "PhotDetector", "PhotSource",
               "PhotMaterial", "PhotAnalyzerOutput",
               "PhotMaterial", "PhotMaterialProperty", "PhotFigure"]


def object_to_dict(obj, obj_list: list = None) -> dict:
    if obj_list is None:
        obj_list = OBJECT_LIST
    object_dict = {}
    for attribute in getattr(obj, "__dict__"):
        value = getattr(obj, attribute)
        new_value = parse_value(value, obj_list)
        if new_value is not None:
            object_dict[attribute] = new_value
    return object_dict


def parse_value(value, obj_list: list = None):
    if obj_list is None:
        obj_list = OBJECT_LIST
    value_type = type(value).__name__
    if value_type in ["str", "int", "bool", "double", "float"]:
        return value
    elif value_type in obj_list:
        return object_to_dict(value, obj_list)
    elif value_type == "dict":
        return traverse_dict(value, obj_list)
    elif value_type == "list":
        return traverse_list(value, obj_list)
    else:
        return None


def traverse_dict(sub_dict: dict, obj_list: list = None) -> dict:
    if obj_list is None:
        obj_list = OBJECT_LIST
    new_dict = {}
    for key, value in sub_dict.items():
        new_value = parse_value(value, obj_list)
        if new_value is not None:
            new_dict[key] = parse_value(new_value, obj_list)
    # if len(new_dict) == 0:
    #     return None
    return new_dict


def traverse_list(sub_list: list, obj_list: list = None) -> list:
    if obj_list is None:
        obj_list = OBJECT_LIST
    new_list = []
    for item in sub_list:
        if type(item) == "double" or type(item) == "float":
            new_list = sub_list
            break
        new_item = parse_value(item, obj_list)
        if new_item is not None:
            new_list.append(new_item)
    # if len(new_list) == 0:
    #     return None
    return new_list
